/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.feilong.lib.javassist.tools.reflect;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;

/**
 * A runtime metaobject.
 *
 * <p>
 * A <code>Metaobject</code> is created for
 * every object at the base level. A different reflective object is
 * associated with a different metaobject.
 *
 * <p>
 * The metaobject intercepts method calls
 * on the reflective object at the base-level. To change the behavior
 * of the method calls, a subclass of <code>Metaobject</code>
 * should be defined.
 *
 * <p>
 * To obtain a metaobject, calls <code>_getMetaobject()</code>
 * on a reflective object. For example,
 *
 * <pre>
 * 
 * Metaobject m = ((Metalevel) reflectiveObject)._getMetaobject();
 * </pre>
 *
 * @see com.feilong.lib.javassist.tools.reflect.ClassMetaobject
 * @see com.feilong.lib.javassist.tools.reflect.Metalevel
 */
public class Metaobject implements Serializable{

    /** default serialVersionUID */
    private static final long serialVersionUID = 1L;

    protected ClassMetaobject classmetaobject;

    protected Metalevel       baseobject;

    protected Method[]        methods;

    /**
     * Constructs a <code>Metaobject</code>. The metaobject is
     * constructed before the constructor is called on the base-level
     * object.
     *
     * @param self
     *            the object that this metaobject is associated with.
     * @param args
     *            the parameters passed to the constructor of
     *            <code>self</code>.
     */
    public Metaobject(Object self, Object[] args){
        baseobject = (Metalevel) self;
        classmetaobject = baseobject._getClass();
        methods = classmetaobject.getReflectiveMethods();
    }

    /**
     * Constructs a <code>Metaobject</code> without initialization.
     * If calling this constructor, a subclass should be responsible
     * for initialization.
     */
    protected Metaobject(){
        baseobject = null;
        classmetaobject = null;
        methods = null;
    }

    private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeObject(baseobject);
    }

    private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
        baseobject = (Metalevel) in.readObject();
        classmetaobject = baseobject._getClass();
        methods = classmetaobject.getReflectiveMethods();
    }

    /**
     * Obtains the class metaobject associated with this metaobject.
     *
     * @see com.feilong.lib.javassist.tools.reflect.ClassMetaobject
     */
    public final ClassMetaobject getClassMetaobject(){
        return classmetaobject;
    }

    /**
     * Obtains the object controlled by this metaobject.
     */
    public final Object getObject(){
        return baseobject;
    }

    /**
     * Changes the object controlled by this metaobject.
     *
     * @param self
     *            the object
     */
    public final void setObject(Object self){
        baseobject = (Metalevel) self;
        classmetaobject = baseobject._getClass();
        methods = classmetaobject.getReflectiveMethods();

        // call _setMetaobject() after the metaobject is settled.
        baseobject._setMetaobject(this);
    }

    /**
     * Returns the name of the method specified
     * by <code>identifier</code>.
     */
    public final String getMethodName(int identifier){
        String mname = methods[identifier].getName();
        int j = ClassMetaobject.methodPrefixLen;
        for (;;){
            char c = mname.charAt(j++);
            if (c < '0' || '9' < c){
                break;
            }
        }

        return mname.substring(j);
    }

    /**
     * Returns an array of <code>Class</code> objects representing the
     * formal parameter types of the method specified
     * by <code>identifier</code>.
     */
    public final Class<?>[] getParameterTypes(int identifier){
        return methods[identifier].getParameterTypes();
    }

    /**
     * Returns a <code>Class</code> objects representing the
     * return type of the method specified by <code>identifier</code>.
     */
    public final Class<?> getReturnType(int identifier){
        return methods[identifier].getReturnType();
    }

    /**
     * Is invoked when base-level method invocation is intercepted.
     * This method simply executes the intercepted method invocation
     * with the original parameters and returns the resulting value.
     *
     * <p>
     * Every subclass of this class should redefine this method.
     *
     * <p>
     * Note: this method is not invoked if the base-level method
     * is invoked by a constructor in the super class. For example,
     *
     * <pre>
     * abstract class A{
     * 
     *     abstract void initialize();
     * 
     *     A(){
     *         initialize(); // not intercepted
     *     }
     * }
     *
     * class B extends A{
     * 
     *     void initialize(){
     *         System.out.println("initialize()");
     *     }
     * 
     *     B(){
     *         super();
     *         initialize(); // intercepted
     *     }
     * }
     * </pre>
     *
     * <p>
     * if an instance of B is created,
     * the invocation of initialize() in B is intercepted only once.
     * The first invocation by the constructor in A is not intercepted.
     * This is because the link between a base-level object and a
     * metaobject is not created until the execution of a
     * constructor of the super class finishes.
     */
    public Object trapMethodcall(int identifier,Object[] args) throws Throwable{
        try{
            return methods[identifier].invoke(getObject(), args);
        }catch (java.lang.reflect.InvocationTargetException e){
            throw e.getTargetException();
        }catch (java.lang.IllegalAccessException e){
            throw new CannotInvokeException(e);
        }
    }
}
